package ar.edu.itba.pod.legajo48062;

import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import ar.edu.itba.balance.api.AgentsBalancer;
import ar.edu.itba.balance.api.NodeAgent;
import ar.edu.itba.node.Node;
import ar.edu.itba.node.NodeInformation;
import ar.edu.itba.node.api.ClusterAdministration;
import ar.edu.itba.pod.agent.runner.Agent;

public class Cluster extends UnicastRemoteObject implements ClusterAdministration {

	private static final long serialVersionUID = 1L;

	private Set<NodeInformation> nodeList;
	private NodeInformation me;
	private String groupId;
	private DistributedAgentsBalancer agentsBalancer;
	private DistributedSimulation node;
	
	protected Cluster(NodeInformation node) throws RemoteException {
		super();
		nodeList = new CopyOnWriteArraySet<NodeInformation>();
		me=node;
	}

	public void setNode(DistributedSimulation node){
		this.node=(DistributedSimulation) node;
	}

	@Override
	public Set<NodeInformation> addNewNode(NodeInformation node) throws RemoteException, NotBoundException {
		final NodeInformation localNode = node;
		if (nodeList.add(node)){
			new Communication<ClusterAdministration>(this,Node.CLUSTER_COMUNICATION){
				@Override
				public void remoteCall(ClusterAdministration stub, NodeInformation node) throws RemoteException, NotBoundException {
					stub.addNewNode(localNode);
				}
			}.broadcast(null,true, true);
		}
		
		return nodeList;
	}

	@Override
	public void connectToGroup(String host, int port) throws RemoteException {
		new Communication<ClusterAdministration>(this,Node.CLUSTER_COMUNICATION){
			@Override
			public void remoteCall(ClusterAdministration stub, NodeInformation node) throws RemoteException, NotBoundException {
				if (stub.isConnectedToGroup()){
					groupId=stub.getGroupId();
					nodeList = stub.addNewNode(me);
					agentsBalancer.bullyElection(me, Host.getTime());
				}
			}
		}.unicast(new NodeInformation(host, port, ""),false);
	}

	@Override
	public Set<NodeInformation> connectedNodes() throws RemoteException {
		return nodeList;
	}

	@Override
	public void createGroup() throws RemoteException {
		groupId="Cluster creado por "+me.id();
		nodeList.add(me);
		agentsBalancer.bullyCoordinator(me, Host.getTime());
	}

	@Override
	public void disconnectFromGroup(NodeInformation node) throws RemoteException, NotBoundException {
		final NodeInformation localNode = node;
		if (nodeList!=null){
			if (nodeList.contains(node)){
				System.out.println("DESCONECTO A "+node.id());
				nodeList.remove(node);
				
				new Communication<ClusterAdministration>(this,Node.CLUSTER_COMUNICATION){
					@Override
					public void remoteCall(ClusterAdministration stub, NodeInformation node) throws RemoteException, NotBoundException {
						stub.disconnectFromGroup(localNode);
					}
				}.broadcast(null,false, true);				
			}
		}
		if (node.equals(me)){
			// SI ME DESCONECTARON A MI, SALGO DEL CLUSTER
			nodeList.clear();
			groupId=null;
		}else{
			// SI DESCONECTARON AL COORDINADOR LANZO UNA ELECCION
			if (agentsBalancer.getCoordinator().equals(node)){
				agentsBalancer.bullyElection(me, Host.getTime());
			}
		}
	}
	
	// CUANDO SE LLAMA A DISCONNECTFROMGROUP LOCALMENTE NUNCA DEBERIA DAR REMOTEEXCEPTION, CREO ESTA FUNCION
	// PARA QUE SE CUANDO SE LLAMA LOCALMENTE Y MANEJAR LA EXCEPCION UNA UNICA VEZ
	public void disconnectFromGroupLocal(NodeInformation node){
		try {
			System.out.println("SE CAYO EL NODO "+node.id());
			disconnectFromGroup(node);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public String getGroupId() throws RemoteException {
		return groupId;
	}

	@Override
	public boolean isConnectedToGroup() throws RemoteException {
		return groupId!=null;
	}
	
	public NodeInformation me(){
		return me;
	}

	public void setAgentsBalancer(DistributedAgentsBalancer agentsBalancer){
		this.agentsBalancer=agentsBalancer;
	}
	
	public NodeInformation getCoordinator(){
		return this.agentsBalancer.getCoordinator();
	}
	
	public void shutdown() throws RemoteException{
		if (connectedNodes().size()>1){
			// SI ME ESTOY DESCONECTANDO Y HAY MAS NODOS ADEMAS DE MI, LE MANDO AL COORDINADOR MIS AGENTES CON SHUTDOWN
			boolean success=false;
			final List<NodeAgent> nodeAgents = new ArrayList<NodeAgent>();
			for (Agent a : node.getAgentsRunning()){
				nodeAgents.add(new NodeAgent(me,a));
			}
			
			while (!success){
				success = new Communication<AgentsBalancer>(this,Node.AGENTS_BALANCER){
					@Override
					public void remoteCall(AgentsBalancer stub, NodeInformation node) throws Exception {
						stub.shutdown(nodeAgents);
					}
				}.unicast(agentsBalancer.getCoordinator(), false);		
				if (!success){
					System.out.println("NO SE PUDO MANDAR EL UNICAST DE SHUTDOWN AL COORDINADOR!!!");
				}		
			}
		}else{
			System.out.println("ME DESCONECTE Y ERA EL ULTIMO DEL CLUSTER, SE TERMINO TODA LA SIMULACION");
		}
		
		for (Agent a : node.getAgentsRunning()){
			node.remove(a);
		}
		
		disconnectFromGroupLocal(me);
	}
	
}
